<?php

namespace JLPay\Services;

use JLPay\Core\OrgConfig;
use JLPay\Core\SignVerifier;
use JLPay\Models\Upload\ImageUploadRequest;
use JLPay\Models\Upload\VideoUploadRequest;
use JLPay\Models\Upload\PdfUploadRequest;
use JLPay\Models\Upload\FileUploadResponse;
use JLPay\Utils\Logger;
use JLPay\Utils\SmUtils;
use JLPay\Exceptions\HttpExecutionException;
use JLPay\Exceptions\SignVerifyException;
use JLPay\Exceptions\ConfigException;
use CurlFile;
use Exception;

/**
 * 文件服务类 - 独立的文件传输服务
 */
class FileService
{
    protected OrgConfig $config;
    protected SignVerifier $signVerifier;
    protected Logger $logger;

    /**
     * 初始化文件服务
     *
     * @param OrgConfig $config 配置对象
     */
    public function __construct(OrgConfig $config)
    {
        $this->config = $config;
        $this->signVerifier = new SignVerifier($config);
        $this->logger = Logger::getInstance();
    }

    /**
     * 获取API URL
     *
     * @param string $requestClass 请求类名
     * @param string|null $apiPath 接口路径
     * @return string 完整的API URL
     */
    public function getApiUrl(string $requestClass, ?string $apiPath = null): string
    {
        if ($apiPath) {
            return $this->config->getUrl() . $apiPath;
        }

        // 创建请求实例
        $requestInstance = new $requestClass();

        // 优先使用getApiUri方法
        if (method_exists($requestInstance, 'getApiUri')) {
            return $this->config->getUrl() . $requestInstance->getApiUri();
        }

        // 使用API_PREFIX和API_URI常量组合
        $apiPrefix = defined($requestClass . '::API_PREFIX') ? constant($requestClass . '::API_PREFIX') : '';
        $apiUri = defined($requestClass . '::API_URI') ? constant($requestClass . '::API_URI') : '';

        return $this->config->getUrl() . $apiPrefix . $apiUri;
    }

    /**
     * 发送POST请求（文件上传）
     *
     * @param object $request 请求对象
     * @param string $responseClass 响应类名
     * @return object 响应对象
     * @throws ConfigException
     * @throws HttpExecutionException
     * @throws SignVerifyException
     */
    public function post(object $request, string $responseClass): object
    {
        // 构建完整URL
        $url = $this->getApiUrl(get_class($request));
        $this->logger->debug("文件上传请求URL: {$url}");

        // 获取URI部分用于签名
        $uri = $this->extractUri($url);

        try {
            // 准备文件数据
            $files = [];
            if (method_exists($request, 'getFile') && $request->getFile()) {
                $fileName = method_exists($request, 'getFileName') ? $request->getFileName() : 'file';
                $files['file'] = $this->prepareFileForUpload($request->getFile(), $fileName);
            }

            // 准备表单数据
            $data = [];
            if (method_exists($request, 'getMeta') && $request->getMeta()) {
                $data['meta'] = $request->getMeta();
            }

            // 使用meta进行签名（而不是body）
            $metaForSign = $data['meta'] ?? '{}';
            [$signValue, $timestamp, $nonce] = $this->signVerifier->sign('POST', $uri, $metaForSign);

            // 添加认证和签名头
            $headers = [
                'x-jlpay-appid: ' . $this->config->getAppId(),
                'x-jlpay-nonce: ' . $nonce,
                'x-jlpay-timestamp: ' . $timestamp,
                'x-jlpay-sign-alg: SM3WithSM2WithDer',
                'x-jlpay-sign: ' . $signValue
            ];

            $this->logger->debug("=== 文件上传请求信息 ===");
            $this->logger->debug("请求URL: {$url}");
            $this->logger->debug("请求方法: POST");
            $this->logger->debug("请求头:", $headers);
            $this->logger->debug("表单数据: " . json_encode($data, JSON_UNESCAPED_UNICODE));
            $this->logger->debug("文件数据: " . (empty($files) ? '未设置' : '已设置'));
            $this->logger->debug("=========================");

            // 发送请求
            $response = $this->sendCurlRequest($url, $headers, $data, $files);

            // 解析响应
            $responseData = json_decode($response['body'], true);
            if ($responseData === null) {
                throw new HttpExecutionException("解析响应JSON失败: " . json_last_error_msg());
            }

            $this->logger->debug("文件上传响应: " . json_encode($responseData, JSON_UNESCAPED_UNICODE));

            // 验证响应签名
            if (isset($response['headers']['x-jlpay-sign'])) {
                $this->signVerifier->verify(
                    'POST',
                    $uri,
                    $response['headers']['x-jlpay-timestamp'] ?? '',
                    $response['headers']['x-jlpay-nonce'] ?? '',
                    $response['body'],
                    $response['headers']['x-jlpay-sign']
                );
            }

            // 创建响应对象
            return new $responseClass($responseData);

        } catch (Exception $e) {
            if ($e instanceof SignVerifyException) {
                $this->logger->error("响应签名验证失败: " . $e->getMessage());
                throw $e;
            }
            
            $this->logger->error("文件上传过程中发生错误: " . $e->getMessage());
            throw new HttpExecutionException("文件上传失败: " . $e->getMessage(), 0, $e);
        }
    }

    /**
     * 上传图片
     *
     * @param ImageUploadRequest $request 图片上传请求
     * @return FileUploadResponse 文件上传响应
     * @throws ConfigException
     * @throws HttpExecutionException
     * @throws SignVerifyException
     */
    public function uploadImage(ImageUploadRequest $request): FileUploadResponse
    {
        return $this->post($request, FileUploadResponse::class);
    }

    /**
     * 上传视频
     *
     * @param VideoUploadRequest $request 视频上传请求
     * @return FileUploadResponse 文件上传响应
     * @throws ConfigException
     * @throws HttpExecutionException
     * @throws SignVerifyException
     */
    public function uploadVideo(VideoUploadRequest $request): FileUploadResponse
    {
        return $this->post($request, FileUploadResponse::class);
    }

    /**
     * 上传PDF
     *
     * @param PdfUploadRequest $request PDF上传请求
     * @return FileUploadResponse 文件上传响应
     * @throws ConfigException
     * @throws HttpExecutionException
     * @throws SignVerifyException
     */
    public function uploadPdf(PdfUploadRequest $request): FileUploadResponse
    {
        return $this->post($request, FileUploadResponse::class);
    }

    /**
     * 从URL中提取URI部分
     *
     * @param string $url 完整URL
     * @return string URI部分
     */
    private function extractUri(string $url): string
    {
        $parsed = parse_url($url);
        $uri = $parsed['path'] ?? '';
        if (isset($parsed['query'])) {
            $uri .= '?' . $parsed['query'];
        }
        return $uri;
    }

    /**
     * 准备文件用于上传
     *
     * @param resource|string $file 文件内容或文件资源
     * @param string $fileName 文件名
     * @return CurlFile|string
     */
    private function prepareFileForUpload($file, string $fileName)
    {
        if (is_resource($file)) {
            // 如果是文件资源，创建临时文件
            $tempFile = tempnam(sys_get_temp_dir(), 'jlpay_upload_');
            $tempHandle = fopen($tempFile, 'wb');
            
            rewind($file);
            stream_copy_to_stream($file, $tempHandle);
            fclose($tempHandle);
            fclose($file);
            
            return new CurlFile($tempFile, $this->getMimeType($fileName), $fileName);
        } elseif (is_string($file)) {
            // 如果是字符串内容，创建临时文件
            $tempFile = tempnam(sys_get_temp_dir(), 'jlpay_upload_');
            file_put_contents($tempFile, $file);
            
            return new CurlFile($tempFile, $this->getMimeType($fileName), $fileName);
        }
        
        throw new ConfigException("不支持的文件类型");
    }

    /**
     * 根据文件名获取MIME类型
     *
     * @param string $fileName 文件名
     * @return string MIME类型
     */
    private function getMimeType(string $fileName): string
    {
        $extension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
        
        $mimeTypes = [
            'jpg' => 'image/jpeg',
            'jpeg' => 'image/jpeg',
            'png' => 'image/png',
            'gif' => 'image/gif',
            'bmp' => 'image/bmp',
            'webp' => 'image/webp',
            'mp4' => 'video/mp4',
            'avi' => 'video/avi',
            'mov' => 'video/quicktime',
            'wmv' => 'video/x-ms-wmv',
            'flv' => 'video/x-flv',
            'pdf' => 'application/pdf',
        ];
        
        return $mimeTypes[$extension] ?? 'application/octet-stream';
    }

    /**
     * 发送CURL请求
     *
     * @param string $url 请求URL
     * @param array $headers 请求头
     * @param array $data 表单数据
     * @param array $files 文件数据
     * @return array 响应数据
     * @throws HttpExecutionException
     */
    private function sendCurlRequest(string $url, array $headers, array $data, array $files): array
    {
        $ch = curl_init();
        
        // 合并表单数据和文件数据
        $postData = array_merge($data, $files);
        
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $postData,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER => false,
            CURLOPT_HEADERFUNCTION => [$this, 'handleHeaderLine'],
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_TIMEOUT => 60,
            CURLOPT_CONNECTTIMEOUT => 10,
        ]);
        
        $this->responseHeaders = [];
        $body = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);
        
        if ($body === false) {
            throw new HttpExecutionException("CURL请求失败: {$error}");
        }
        
        if ($httpCode >= 400) {
            throw new HttpExecutionException("HTTP请求失败，状态码: {$httpCode}，响应: " . substr($body, 0, 200));
        }
        
        return [
            'body' => $body,
            'headers' => $this->responseHeaders,
            'status' => $httpCode
        ];
    }

    /**
     * 响应头存储
     * @var array
     */
    private $responseHeaders = [];

    /**
     * 处理响应头行
     *
     * @param resource $ch CURL句柄
     * @param string $header 头行
     * @return int 头行长度
     */
    private function handleHeaderLine($ch, string $header): int
    {
        $len = strlen($header);
        $header = trim($header);
        
        if (empty($header) || strpos($header, 'HTTP/') === 0) {
            return $len;
        }
        
        $parts = explode(':', $header, 2);
        if (count($parts) === 2) {
            $name = strtolower(trim($parts[0]));
            $value = trim($parts[1]);
            $this->responseHeaders[$name] = $value;
        }
        
        return $len;
    }
} 